<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8" />
    <title>Projects API (OpenAPI 草稿)</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@4/swagger-ui.css" />
    <style>
      body {
        margin: 0;
      }
      #swagger {
        height: 100vh;
      }
      .info-note {
        padding: 10px;
        background: #fafafa;
        border-bottom: 1px solid #eee;
        font-size: 13px;
      }
    </style>
  </head>
  <body>
    <div class="info-note">
      说明：AI生成。此页面包含 OpenAPI 3.0 草稿。可以直接给后端或在本地打开查看。若要用 Swagger UI
      的在线调试功能，请替换 `servers` 的 URL 为实际后端地址，并在右上角设置认证 token。
    </div>

    <div id="swagger"></div>

    <!-- 内嵌 YAML -->
    <script id="openapi-spec" type="text/yaml">
      openapi: 3.0.3
      info:
        title: Projects & Tasks API (草稿)
        version: 0.1.0
        description: |
          Minimal API contract for the "projects" table used by the front-end.
          Each project contains multiple tasks (taskKey: duijie, zhengti, ...).
          All resources include createdAt / updatedAt / deletedAt timestamps (ISO 8601, recommend UTC/Z).
      servers:
        - url: https://api.example.com
          description: Production (placeholder)
      components:
        securitySchemes:
          bearerAuth:
            type: http
            scheme: bearer
            bearerFormat: JWT
        schemas:
          ErrorResponse:
            type: object
            properties:
              error:
                type: boolean
                example: true
              code:
                type: integer
                example: 400
              message:
                type: string
                example: "Bad Request"
              details:
                type: object
                nullable: true
          Task:
            type: object
            properties:
              person:
                type: array
                items:
                  type: string
                description: "人员数组，空数组表示无人员"
                example: ["张三"]
              planMonth:
                type: integer
                description: "计划月（1-12）或 null"
                nullable: true
                example: 8
              planDay:
                type: integer
                description: "计划日（1-31）或 null"
                nullable: true
                example: 12
              actualMonth:
                type: integer
                nullable: true
                example: 8
              actualDay:
                type: integer
                nullable: true
                example: 13
              state:
                type: string
                nullable: true
                description: "任务状态：pending | done"
                enum: [ pending, done, null ]
                example: pending
              createdAt:
                type: string
                format: date-time
                example: "2025-08-01T08:03:00Z"
              updatedAt:
                type: string
                format: date-time
                example: "2025-08-05T09:10:00Z"
              deletedAt:
                type: string
                format: date-time
                nullable: true
                example: null
          Project:
            type: object
            properties:
              key:
                type: string
                description: "唯一标识（后端可以用 id 替代）"
                example: "123"
              project:
                type: string
                example: "项目A"
              yearMonth:
                type: string
                nullable: true
                description: "YYYY-MM 或 null/\"\""
                example: "2025-08"
              tasks:
                type: object
                additionalProperties:
                  $ref: "#/components/schemas/Task"
                description: "以 taskKey 为子属性（duijie, zhengti, ...）"
              createdAt:
                type: string
                format: date-time
                example: "2025-08-01T08:00:00Z"
              updatedAt:
                type: string
                format: date-time
                example: "2025-08-11T12:00:00Z"
              deletedAt:
                type: string
                format: date-time
                nullable: true
                example: null
          ProjectsListResponse:
            type: object
            properties:
              total:
                type: integer
                example: 123
              data:
                type: array
                items:
                  $ref: "#/components/schemas/Project"
        parameters:
          page:
            name: page
            in: query
            schema:
              type: integer
              minimum: 1
              default: 1
          pageSize:
            name: pageSize
            in: query
            schema:
              type: integer
              default: 10
              enum: [10,20,50,100]
          month:
            name: month
            in: query
            schema:
              type: string
              pattern: '^\d{4}-\d{2}$'
            description: "筛选 yearMonth（格式 YYYY-MM）"
          search:
            name: search
            in: query
            schema:
              type: string
            description: "按 project 名模糊搜索"
          sortBy:
            name: sortBy
            in: query
            schema:
              type: string
              enum: [ yearMonth, project, updatedAt ]
            description: "排序字段"
          sortOrder:
            name: sortOrder
            in: query
            schema:
              type: string
              enum: [ asc, desc ]
            description: "排序方向"
      paths:
        /projects:
          get:
            summary: 获取项目列表（支持分页、筛选、排序）
            security:
              - bearerAuth: []
            parameters:
              - $ref: "#/components/parameters/page"
              - $ref: "#/components/parameters/pageSize"
              - $ref: "#/components/parameters/month"
              - $ref: "#/components/parameters/search"
              - $ref: "#/components/parameters/sortBy"
              - $ref: "#/components/parameters/sortOrder"
            responses:
              "200":
                description: 列表（含总数）
                content:
                  application/json:
                    schema:
                      $ref: "#/components/schemas/ProjectsListResponse"
              "401":
                description: 未授权
                content:
                  application/json:
                    schema:
                      $ref: "#/components/schemas/ErrorResponse"
          post:
            summary: 创建新项目
            security:
              - bearerAuth: []
            requestBody:
              required: true
              content:
                application/json:
                  schema:
                    type: object
                    properties:
                      project:
                        type: string
                      yearMonth:
                        type: string
                      tasks:
                        type: object
                        additionalProperties:
                          $ref: "#/components/schemas/Task"
                  example:
                    project: "新项目"
                    yearMonth: "2025-09"
                    tasks: {}
            responses:
              "201":
                description: 创建成功，返回新资源（包含 createdAt/updatedAt）
                content:
                  application/json:
                    schema:
                      $ref: "#/components/schemas/Project"
              "400":
                description: 校验错误
                content:
                  application/json:
                    schema:
                      $ref: "#/components/schemas/ErrorResponse"
        /projects/{projectId}:
          get:
            summary: 读取单条项目（含 tasks）
            security:
              - bearerAuth: []
            parameters:
              - name: projectId
                in: path
                required: true
                schema:
                  type: string
            responses:
              "200":
                description: 单个项目
                content:
                  application/json:
                    schema:
                      $ref: "#/components/schemas/Project"
              "404":
                description: 未找到
                content:
                  application/json:
                    schema:
                      $ref: "#/components/schemas/ErrorResponse"
          put:
            summary: 更新整行（替换）
            security:
              - bearerAuth: []
            parameters:
              - name: projectId
                in: path
                required: true
                schema:
                  type: string
            requestBody:
              required: true
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/Project"
            responses:
              "200":
                description: 更新后的资源（含 updatedAt）
                content:
                  application/json:
                    schema:
                      $ref: "#/components/schemas/Project"
              "412":
                description: 前置条件失败（并发锁），If-Unmodified-Since 不匹配
                content:
                  application/json:
                    schema:
                      $ref: "#/components/schemas/ErrorResponse"
          delete:
            summary: 删除整行（建议后端支持软删除 -> 设置 deletedAt）
            security:
              - bearerAuth: []
            parameters:
              - name: projectId
                in: path
                required: true
                schema:
                  type: string
            responses:
              "204":
                description: 删除成功（无内容）
              "404":
                description: 未找到
                content:
                  application/json:
                    schema:
                      $ref: "#/components/schemas/ErrorResponse"
        /projects/{projectId}/tasks/{taskKey}:
          patch:
            summary: 更新单个任务（部分更新 task 字段）
            security:
              - bearerAuth: []
            parameters:
              - name: projectId
                in: path
                required: true
                schema:
                  type: string
              - name: taskKey
                in: path
                required: true
                schema:
                  type: string
                  example: duijie
            requestBody:
              required: true
              content:
                application/json:
                  schema:
                    type: object
                    description: "只需包含要修改的字段"
                    properties:
                      person:
                        type: array
                        items:
                          type: string
                      planMonth:
                        type: integer
                        nullable: true
                      planDay:
                        type: integer
                        nullable: true
                      actualMonth:
                        type: integer
                        nullable: true
                      actualDay:
                        type: integer
                        nullable: true
                      state:
                        type: string
                        enum: [pending, done, null]
                  example:
                    person: ["张三"]
                    planMonth: 8
                    planDay: 12
                    actualMonth: 8
                    actualDay: 13
                    state: done
            responses:
              "200":
                description: 返回更新后的 task（含 updatedAt）
                content:
                  application/json:
                    schema:
                      type: object
                      properties:
                        task:
                          $ref: "#/components/schemas/Task"
              "400":
                description: 校验错误
                content:
                  application/json:
                    schema:
                      $ref: "#/components/schemas/ErrorResponse"
              "404":
                description: 项目或 task 未找到
                content:
                  application/json:
                    schema:
                      $ref: "#/components/schemas/ErrorResponse"
          delete:
            summary: 删除 / 清空单个 task（可选：后端返回 204 或返回更新后的 task）
            security:
              - bearerAuth: []
            parameters:
              - name: projectId
                in: path
                required: true
                schema:
                  type: string
              - name: taskKey
                in: path
                required: true
                schema:
                  type: string
            responses:
              "204":
                description: 删除/清空成功
              "404":
                description: 未找到
                content:
                  application/json:
                    schema:
                      $ref: "#/components/schemas/ErrorResponse"
      security:
        - bearerAuth: []
    </script>

    <script src="https://cdn.jsdelivr.net/npm/js-yaml@4.1.0/dist/js-yaml.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@4/swagger-ui-bundle.js"></script>
    <script>
      ;(function () {
        try {
          const yamlText = document.getElementById('openapi-spec').textContent
          const spec = jsyaml.load(yamlText)
          const ui = SwaggerUIBundle({
            spec,
            dom_id: '#swagger',
            presets: [SwaggerUIBundle.presets.apis],
            layout: 'BaseLayout',
            docExpansion: 'none',
            operationsSorter: 'alpha',
          })
          window.ui = ui
        } catch (err) {
          document.getElementById('swagger').innerText = '加载 OpenAPI spec 出错：' + err
          throw err
        }
      })()
    </script>
  </body>
</html>
